package nachos.threads;

import nachos.machine.*;

import java.util.LinkedList;

/**
 * 使用硬件计时器提供抢占，并允许线程睡眠到特定时间
 */
public class Alarm {
    /**
     * 分配一个新的警报器
     * 将机器的计时器中断处理程序设置为此警报器的回调
     * <p>
     * Note: Nachos 无法在多个警报器的情况下正常工作
     */
    public Alarm() {
        // 链表初始化
        list = new LinkedList<WakeInfo>();

        Machine.timer().setInterruptHandler(new Runnable() {
            public void run() {
                timerInterrupt();
            }
        });
    }

    /**
     * 计时器中断处理程序
     * 它会由机器的定时器定期调用（大约每 500 个时钟周期）
     * 使当前线程让步（yield）
     * 如果有另外一个线程应该运行，那么强制进行上下文切换
     */
    public void timerInterrupt() {
        // 关中断，并获取之前的中断状态
        boolean intStatus = Machine.interrupt().disable();

        // 遍历链表中的每一个元素
        for (WakeInfo wakeInfo : list) {
            // 如果当前时间超过线程的唤醒时间
            if (Machine.timer().getTime() >= wakeInfo.getWakeTime()) {
                // 将该线程添加到就绪队列
                wakeInfo.getThread().ready();
                // 从线程链表中移出这个元素
                list.remove(wakeInfo);
            }
        }

        // 当前线程让出 CPU
        KThread.currentThread().yield();

        // 恢复中断
        Machine.interrupt().restore(intStatus);
    }

    /**
     * 将当前线程置于休眠状态至少 x 个时钟周期，在计时器中断处理程序中将其唤醒
     * 在 (current time) >= (WaitUntil called time)+(x) 的第一个计时器中断期间，必须唤醒线程（放到调度程序的就绪集合中）
     *
     * @param x 等待的最小时钟周期数
     * @see nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
        // 关中断，并获取之前的中断状态
        boolean intStatus = Machine.interrupt().disable();

        // 计算被唤醒的时间
        long wakeTime = Machine.timer().getTime() + x;

        // 创建一个 WakeInfo，并将它添加到链表中去
        list.add(new WakeInfo(KThread.currentThread(), wakeTime));

        // 让当前线程进入睡眠状态
        KThread.currentThread().sleep();

        // 恢复中断
        Machine.interrupt().restore(intStatus);
    }

    // 等待中的线程以及它的唤醒时间
    private static class WakeInfo {
        private KThread thread;
        private long wakeTime;

        private WakeInfo(KThread thread, long waitTime) {
            this.thread = thread;
            this.wakeTime = waitTime;
        }

        private KThread getThread() {
            return this.thread;
        }

        private long getWakeTime() {
            return this.wakeTime;
        }
    }

    LinkedList<WakeInfo> list;
}
